<!-- 角色会话 -->

<template>
	<div style="height: 100%">
		<a-row :gutter="24" type="flex" align="stretch" style="height: 100%">
			<a-col :span="24" class="mb-24">
				<a-card :bordered="false" class="header-solid h-full" :bodyStyle="{ height: '100%' }">
					<a-col :span="6" style="height: 100%">
						<div class="dialog_box">
							<div class="list">
								<a-collapse v-model="roleActiveKey">
									<a-collapse-panel :header="item.title" v-for="item in roleData" :key="item.id">
										<div class="item" :id="'role_item_' + list.id" :class="{ active: list.id == roleActiveId }" v-for="list in item.list" :key="'role_' + list.id" @click="roleItem(list.id)">
											<div class="line1 c-line-1">{{ list.title }}</div>
											<div class="line2 c-line-2">{{ list.desc }}</div>
										</div>
									</a-collapse-panel>
								</a-collapse>
							</div>
							<div class="foot">
								<span class="c-pointer" @click="clearAllChat">清空所有会话</span>
							</div>
						</div>
					</a-col>
					<a-col :span="18" style="height: 100%">
						<div class="content_box">
							<div class="output" id="output">
								<div class="list_box" :class="{ active: dItem.id == roleActiveId }" v-if="dItem.id == roleActiveId" v-for="dItem in roleDataItem()" :key="'rlist_' + dItem.id">
									<div class="list" v-for="(item, index) in dItem.list" :key="'msg_' + item.id">
										<div class="item right" v-if="item.message">
											<div class="message">
												<v-md-editor v-model="item.message" mode="preview"></v-md-editor>
											</div>
											<div class="avatar">
												<img :src="userInfo.avatar" alt="" />
											</div>
										</div>
										<div class="item left">
											<div class="avatar">
												<img src="@/assets/imgs/logo.png" alt="" />
											</div>
											<div class="message" v-if="item.response">
												<v-md-editor v-model="item.response" left-toolbar="undo redo | todo-list" mode="preview"></v-md-editor>
												<div class="tools" v-if="!item.transmit">
													<div class="copy" @click="messageCopy(item.response)">
														<a-icon type="copy" />
														<span>复制</span>
													</div>
													<div class="collect" v-if="item.message && item.id && item.id != 'welcome' && item.id != 'message'" @click="messageCollect(item)">
														<a-icon type="heart" :theme="item.vote == 0 ? 'outlined' : 'filled'" :style="{ color: item.vote == 0 ? '' : '#FF3434' }" />
														<span>收藏</span>
													</div>
												</div>
											</div>
											<div class="message" v-else>
												<a-icon type="loading" :style="{ fontSize: '26px' }" />
											</div>
										</div>
									</div>
								</div>
							</div>
							<div class="tools">
								<a-radio-group v-model="modeType" button-style="solid" size="small">
									<a-radio-button :value="item.type" v-for="(item, index) in modeList" :key="'g_mode_' + index">{{ item.name }}（{{ item.usable }}积分）</a-radio-button>
								</a-radio-group>
								<div class="clear c-pointer" @click="clearAloneChat">清空对话</div>
							</div>
							<div class="input">
								<textarea class="textarea" v-model="inputText" placeholder="请输入内容（Enter 发送消息 / Ctrl + Enter 换行）" @keyup.enter="handleKeyCode"></textarea>
								<div class="button">
									<a-button type="primary" size="small" :disabled="!disabled" @click="sendMessage">发送</a-button>
								</div>
							</div>
						</div>
					</a-col>
				</a-card>
			</a-col>
		</a-row>
	</div>
</template>

<script>
	import { mapState, mapGetters, mapMutations, mapActions } from "vuex"
	export default {
		components: {},
		data() {
			return {
				inputText: "", // 发送数据
				disabled: false, // 按钮状态
				// 角色
				roleData: [],
				roleActiveId: 0, // 当前会话ID
				roleActiveKey: [], // 一级分类ID数组
				roleFirstId: "", // 一级分类ID
				// 模型
				modeList: [], // 模型列表
				modeType: "" // 选中模型
			}
		},
		computed: {
			...mapGetters("user", ["token", "userInfo"])
		},
		watch: {
			inputText(newValue, oldValue) {
				this.disabled = newValue == "" ? false : true
			}
		},
		created() {
			this.getRoleList()
			this.getModeList()
		},
		mounted() {},
		methods: {
			// 清除单个对话
			clearAloneChat() {
				let verify = true
				this.roleData.map(item => {
					item.list.map(list => {
						if (list.id == this.roleActiveId) if (list.transmit) verify = false
					})
				})

				if (!verify) {
					this.$message.warning("请等待当前对话完成...")
					return
				}

				this.$confirm({
					title: "确定要清空当前对话吗？",
					content: "",
					okText: "清空",
					okType: "danger",
					cancelText: "取消",
					onOk: async () => {
						return await new Promise((resolve, reject) => {
							this.$http("chat.clearAlone", { type: "rule", prompt_id: this.roleActiveId }).then(res => {
								if (res.code === 1) {
									this.$message.success("清空成功")
									this.getRoleHistory()
									resolve()
								} else {
									reject(res.msg)
								}
							})
						}).catch(error => {
							this.$message.error(error)
						})
					},
					onCancel() {}
				})
			},
			// 清空所有对话
			clearAllChat() {
				let verify = true
				this.roleData.map(item => {
					item.list.map(list => {
						if (list.id == this.roleActiveId) if (list.transmit) verify = false
					})
				})

				if (!verify) {
					this.$message.warning("请等待当前对话完成...")
					return
				}

				this.$confirm({
					title: "确定要清空所有会话吗？",
					content: () => <div style="color: red;">清空后数据不可恢复!</div>,
					okText: "清空",
					okType: "danger",
					cancelText: "取消",
					onOk: async () => {
						return await new Promise((resolve, reject) => {
							this.$http("chat.clearAll", { type: "rule" }).then(res => {
								if (res.code === 1) {
									this.$message.success("清空成功")
									this.getRoleList()
									resolve()
								} else {
									reject(res.msg)
								}
							})
						}).catch(error => {
							this.$message.error(error)
						})
					},
					onCancel() {}
				})
			},
			// 获取GPT模型
			getModeList() {
				this.$http("chat.mode").then(res => {
					if (res.code === 1) {
						this.modeList = res.data
						this.modeType = res.data[0].type
					}
				})
			},
			// 合并角色数据
			roleDataItem() {
				let arr = []
				this.roleData.map(item => {
					item.list.map(list => {
						arr.push(list)
					})
				})
				return arr
			},
			// 切换角色类型
			roleItem(id) {
				this.roleActiveId = id
				this.roleData.map(item => {
					item.list.map(list => {
						if (list.id == id) {
							if (!list.transmit && !list.list.length) this.getRoleHistory()
						}
					})
				})
			},
			// 获取角色列表
			getRoleList() {
				this.$http("role.second").then(res => {
					if (res.code === 1) {
						if (res.data && res.data.length) {
							let obj = {}
							res.data.forEach(item => {
								const key = item.topic_id
								const value = {
									list: [],
									transmit: false,
									...item
								}
								if (!obj[key]) {
									obj[key] = [value]
								} else {
									obj[key].push(value)
								}

								if (item.id == this.$route.query.id) {
									this.roleFirstId = item.topic_id
								}
							})

							let arr = []
							Object.keys(obj).forEach(key => {
								arr.push({
									id: key,
									title: obj[key][0].topic_name,
									list: obj[key]
								})
								this.roleActiveKey.push(key)
							})
							if (this.$route.query.id) {
								this.roleActiveId = this.$route.query.id
							} else {
								this.roleActiveId = arr[0].list[0].id
							}
							this.getRoleHistory()
							this.roleData = arr
							this.$nextTick(() => {
								document.getElementById(`role_item_` + this.roleActiveId).scrollIntoView({ behavior: "smooth", block: "center" })
							})
						}
					}
				})
			},
			// 历史记录
			getRoleHistory() {
				this.$http("role.history", { prompt_id: this.roleActiveId }).then(res => {
					if (res.code === 1) {
						this.roleData.map(item => {
							item.list.map(list => {
								if (list.id == this.roleActiveId) {
									list.list = res.data.reverse()
								}
							})
						})

						this.createRoleChat()
					}
				})
			},
			// 创建角色会话
			createRoleChat() {
				this.$http("role.create", { prompt_id: this.roleActiveId }).then(res => {
					if (res.code === 1) {
						this.roleData.map(item => {
							item.list.map(list => {
								if (list.id == this.roleActiveId) {
									list.group_id = res.data.group_id
									if (!list.list.length) {
										list.list.unshift({
											id: "welcome",
											message: "",
											response: res.data.prompt.welcome,
											transmit: false
										})
									}
								}
							})
						})
					}
				})
			},
			// 信息复制
			async messageCopy(text) {
				try {
					await navigator.clipboard.writeText(text)
					this.$message.success("已复制到剪切板")
				} catch (err) {
					this.$message.error("复制失败")
				}
			},
			// 信息收藏
			messageCollect(item) {
				if (!item.id || item.id == "welcome" || item.id == "message") return
				this.$http("chat.collect", { type: "rule", msg_id: item.id }).then(res => {
					if (res.code === 1) {
						item.vote = item.vote == 0 ? 1 : 0
						this.$message.success(res.msg)
					}
				})
			},
			// 发送内容
			sendMessage() {
				this.roleData.map(item => {
					item.list.map(list => {
						if (list.id == this.roleActiveId) {
							if (list.transmit) {
								this.$message.warning("请等待当前对话完成...")
							} else {
								if (!this.inputText) {
									console.log("输入为空")
									return
								}
								if (!this.modeType) {
									this.$message.error("会话模型错误，请联系管理员")
									return
								}
								this.fetchDataStream(this.inputText, this.roleActiveId, list.group_id, this.modeType)
							}
						}
					})
				})
			},
			// 发送请求
			async fetchDataStream(message, dId, gId, mode) {
				if (!message) return
				this.inputText = ""
				this.roleData.map(item => {
					item.list.map(list => {
						if (list.id == dId) {
							list.transmit = true
							list.list.unshift({
								id: "message",
								message,
								response: "",
								transmit: true,
								vote: 0
							})
						}
					})
				})

				this.disabled = true
				const postData = { type: "rule", message, group_id: gId, prompt_id: dId, mode },
					url = this.$BASE_API + "/addons/chatgpt/web/sendText",
					controller = new AbortController(),
					Token = this.token,
					Sign = window.location.search.replace(/\?/g, "")

				try {
					const response = await fetch(url, {
						method: "post",
						headers: {
							"Content-Type": "application/json;charset=utf-8",
							Token,
							Sign
						},
						body: JSON.stringify(postData),
						signal: controller.signal
					})

					const reader = response.body.getReader()
					let data = ""

					while (true) {
						const { done, value } = await reader.read(),
							str = new TextDecoder().decode(value)

						if (str.indexOf("data: [DONE]") != -1 || str.indexOf("data:[DONE]") != -1 || done) {
							const arr = str.replaceAll(" ", "").split("data:[DONE]")
							if (arr[0].length) {
								this.roleData.map(item => {
									item.list.map(list => {
										if (list.id == dId) {
											list.list[0].response += arr[0]
										}
									})
								})
							}

							this.roleData.map(item => {
								item.list.map(list => {
									if (list.id == dId) {
										list.transmit = false
										list.list[0].transmit = null
										list.list[0].id = arr[1]
									}
								})
							})
							break
						}

						data += str
						this.roleData.map(item => {
							item.list.map(list => {
								if (list.id == dId) {
									list.list[0].response = data
								}
							})
						})
					}
				} catch {
					console.error("请求失败")
				}
			},
			// 监听按键 Enter 发送 / Ctrl + Enter 换行
			handleKeyCode(event) {
				if (event.keyCode == 13) {
					if (!event.ctrlKey) {
						event.preventDefault()
						this.inputText && this.sendMessage() // 发送消息
					} else {
						this.inputText && (this.inputText = this.inputText + "\n") // 换行
					}
				}
			}
		}
	}
</script>

<style lang="scss" scoped>
	::v-deep .ant-collapse {
		border: none;
		background: transparent;
		.ant-collapse-item {
			border: none;
			.ant-collapse-header {
				padding: 12px 16px;

				.ant-collapse-arrow {
					left: 0;
				}
			}
			.ant-collapse-content {
				border: none;
				background: transparent;

				.ant-collapse-content-box {
					padding: 0;
				}
			}
		}
	}

	.dialog_box {
		height: 0;
		min-height: 100%;
		display: flex;
		flex-direction: column;
		border-radius: 12px;
		background: #f7f7f7;
		padding: 12px 0;

		.list {
			flex: 1;
			height: 100%;
			padding: 12px;
			overflow: hidden;
			padding-top: 0;

			&:hover {
				overflow-y: scroll;
				overflow-x: hidden;
				padding-right: 0;
			}

			&::-webkit-scrollbar {
				width: 12px;
			}

			&::-webkit-scrollbar-thumb {
				border-radius: 12px;
				border: 4px solid rgba(0, 0, 0, 0);
				box-shadow: 4px 0 0 #a5adb7 inset;
			}

			&::-webkit-scrollbar-thumb:hover {
				box-shadow: 4px 0 0 #4a4a4a inset;
			}

			// 滚动条两端按钮
			&::-webkit-scrollbar-button {
				height: 10px;
			}

			.item {
				padding: 12px;
				background: #fff;
				border-radius: 10px;
				margin-bottom: 12px;
				cursor: pointer;
				position: relative;

				&:last-child {
					margin-bottom: 0;
				}

				&.active {
					border: 1px solid #1890ff;
					box-shadow: 1px 1px 10px 0 rgba(#1890ff, 0.2);
				}

				.line1 {
					color: #000;
					font-weight: 700;
				}

				.line2 {
					margin-top: 4px;
					color: #999;
				}
			}
		}

		.foot {
			margin-top: 10px;
			text-align: center;

			span {
				font-size: 14px;
				color: #1890ff;
			}
		}
	}
	.content_box {
		height: 0;
		min-height: 100%;
		display: flex;
		flex-direction: column;

		// 滚动条整体
		::-webkit-scrollbar {
			width: 6px;
			height: 6px;
		}
		// 滚动条滑块
		::-webkit-scrollbar-thumb {
			background-clip: padding-box;
			background-color: #a5adb7;
			border: 1px dashed rgba(0, 0, 0, 0);
			border-radius: 6px;
		}
		// 滚动条滑块 hover
		::-webkit-scrollbar-thumb:hover {
			background: #4a4a4a;
		}
		// 滚动条轨道
		::-webkit-scrollbar-track {
			background-color: transparent;
		}
		// 滚动条两端按钮
		::-webkit-scrollbar-button {
			height: 10px;
		}

		.output {
			flex: 1;
			border-radius: 12px;
			background: #f7f7f7;
			overflow: hidden;
			display: flex;
			flex-direction: column;

			.list_box {
				overflow-y: auto;
				overflow-x: hidden;
				display: flex;
				flex-direction: column-reverse;
				position: relative;
				z-index: 1000;
				padding: 20px;
				margin: 0 6px;

				.list {
					margin-bottom: 20px;

					.item {
						margin-bottom: 20px;
						display: flex;
						flex-wrap: nowrap;
						$imgsize: 40px;

						.avatar {
							width: $imgsize;
							height: $imgsize;
							border-radius: 50%;
							overflow: hidden;
							border: 2px solid #fff;

							img {
								width: 100%;
								height: 100%;
								object-fit: cover;
							}
						}

						.message {
							max-width: calc(100% - #{$imgsize + 20px} * 2);
							padding: 10px 12px;

							::v-deep .v-md-editor__right-area {
								width: auto;
								min-width: 0;
							}

							::v-deep .vuepress-markdown-body {
								padding: 0;
							}
						}

						&.left {
							justify-content: flex-start;

							.avatar {
								margin-right: 20px;
							}

							.message {
								border-radius: 0 12px 12px 12px;
								background: #fff;
								color: #000;
								position: relative;

								&:hover {
									.tools {
										display: flex;
									}
								}

								.tools {
									position: absolute;
									bottom: 0;
									left: 0;
									transform: translateY(100%);
									padding-top: 10px;
									display: flex;
									align-items: center;
									display: none;

									.copy,
									.collect {
										padding: 4px 10px;
										border: 1px solid #eee;
										border-radius: 6px;
										cursor: pointer;
										white-space: nowrap;

										&:hover {
											box-shadow: 1px 1px 10px 0 rgba(#1890ff, 0.2);
											background: #fff;
										}

										span {
											margin-left: 4px;
										}
									}

									.collect {
										margin-left: 12px;
									}
								}
							}
						}

						&.right {
							justify-content: flex-end;

							.avatar {
								margin-left: 20px;
							}

							.message {
								border-radius: 12px 0 12px 12px;
								background: $message_color;
								color: #fff;
								::v-deep .vuepress-markdown-body {
									background: $message_color;
									color: #fff;
								}
							}
						}
					}
				}
			}
		}
		.tools {
			padding: 10px 0;
			position: relative;

			.clear {
				position: absolute;
				top: 50%;
				right: 2px;
				transform: translateY(-50%);
				font-size: 14px;
				color: #1890ff;
			}
		}
		.input {
			height: 120px;
			position: relative;
			background: #f7f7f7;
			border-radius: 12px;
			padding: 12px 84px 12px 12px;

			.textarea {
				width: 100%;
				height: 100%;
				border: none;
				outline: none;
				resize: none;
				background: transparent;
				padding: 0;
				margin: 0;
			}

			.button {
				position: absolute;
				bottom: 12px;
				right: 12px;
			}
		}
	}
</style>
